/**
 * Created by haua on 2015/7/3.
 */
(function(){

if(!H)
    H.error('[image.filter]未载入H对象');

/**
 * 给canvas对象设置滤镜的入口
 * ps.canvas 的自带属性width和height直接影响生成的canvas的大小，如果要显示的canvas和实际图像大小不一致，
 * 请把canvas.width和canvas.height设置为实际图像大小，然后通过css控制canvas的显示大小
 * @param filterType string 滤镜名
 * @param option obj 设置
 * @param option.params obj 对滤镜的设置
 * @param option.canvas DOM canvasDOM 改完之后更新的canvas 这个和下面那个必须要有一个。
 * @param option.content obj canvas的2d上下文
 * @param option.oripixel imageData 作为数据源的图像原始数据，如果没有这个，则使用canvasContext作为数据源。如果要还原滤镜，一定要在外部保存一份原始数据，以便还原。
 * @param option.applyArea 要应用滤镜的像素区域，零点在canvas左上角 {x:5,y:5,w:50,h:50} 目前只有马赛克用到
 * @param fn function 回调函数
 * */
H.imageFilter = function(filterType,option,fn){
    if(!filterType)H.error('[H.imageTools]滤镜名的参数为空，你个2b程序猿');
    if(!(option.canvas||option.content))H.error('[H.imageTools]option.canvas&&option.content 两个参数必须有一个，你个2b程序猿');
    option = option || {};
    var params = option.params || {
            blurAmount:1.8,        // 模糊   范围：0-10,精确：0.05
            edgesAmount:1,         // 描线   范围：0-1,精确：0.01
            embossAmount:1,        // 浮雕   范围：0-1,精确：0.01
            greyscaleOpacity:0.54, // 变灰   范围：0-1,精确：0.01
            matrixAmount:2,	       // 矩阵   未知
            mosaicOpacity: 0.45,   // 马赛克 范围：0-1,精确：0.01
            mosaicSize: 4,         // 马赛克 范围：1-40,精确：1
            noiseAmount:16,        // 噪点   范围：0-100,精确：1
            noiseType:"colour",    // 噪点   范围：colour/mono/monochrome
            posterizeAmount:2,     // 减色   范围：2-100,精确：1
            posterizeOpacity:0.11, // 减色   范围：0-1,精确：0.01
            sepiaOpacity:0.5,      // 旧色调 范围：0-1,精确：0.01
            sharpenAmount:0.2,     // 锐化   范围：0-1,精确：0.01
            tintOpacity:0.14,      // 色调   范围：0-1,精确：0.01
            tintColor:'#FF0000'    // 色调
        },
        canvas = option.canvas,
        c = option.content || this.imageFilter.getContext(canvas),
        w = c.width = option.width || c.canvas.width,
        h = c.height = option.height || c.canvas.height,
        applyArea = option.applyArea;
    //console.log(option.oripixel);
    if(c){
        var pixels = option.oripixel?this.imageFilter.copyImageData(option.oripixel):this.imageFilter.getImageData(c), //这里切记不要直接使用option.oripixel作为原始图像数据
            oripixels = option.oripixel?null:this.imageFilter.copyImageData(pixels),
            widthHeight = {width:w,height:h};

        if(pixels){
            var matrix = [];
            if(filterType == 'filter-ori'){
                if(!option.oripixel)H.error('[H.imageFilter]参数出错');
                pixels = option.oripixel;
            }else if(filterType == "filter-blur"){
                pixels = gaussianBlur(widthHeight, pixels, params.blurAmount||params);
            }else if(filterType == "filter-edges"){
                matrix = [
                    0,		1,		0,
                    1,		-4,		1,
                    0,		1,		0
                ];
                pixels = applyMatrix(pixels, matrix, params.edgesAmount);
            }else if(filterType == "filter-emboss"){
                matrix = [
                    -2,		-1,		0,
                    -1,		1,		1,
                    0,		1,		2
                ];
                pixels = applyMatrix(pixels, matrix, params.embossAmount);
            }else if(filterType == "filter-matrix"){
                // 3x3 matrix can be any combination of digits, though to maintain brightness they should add up to 1
                // (-1 x 8 + 9 = 1)
                matrix = [
                    // box blur default
                    0.111,		0.111,		0.111,
                    0.111,		0.111,		0.111,
                    0.111,		0.111,		0.111
                ];
                pixels = applyMatrix(pixels, matrix, params.matrixAmount);
            }
            else if(filterType == "filter-sharpen"){
                matrix = [
                    -1,		-1,		-1,
                    -1,		9,		-1,
                    -1,		-1,		-1
                ];
                pixels = applyMatrix(pixels, matrix, params.sharpenAmount);
            }
            // we need to figure out RGB values for tint, let's do that ahead and not waste time in the loop
            else if(filterType == "filter-tint"){
                var src  = parseInt(createColor(params.tintColor), 16),
                    dest = {r: ((src & 0xFF0000) >> 16), g: ((src & 0x00FF00) >> 8), b: (src & 0x0000FF)};
            }
            if((filterType != "filter-blur") && (filterType != "filter-emboss") && (filterType != "filter-matrix") && (filterType != "filter-sharpen")){
                // the main loop through every pixel to apply the simpler effects
                // (data is per-byte, and there are 4 bytes per pixel, so lets only loop through each pixel and save a few cycles)
                if(filterType=='filter-posterize'){
                    params['posterizeAreas'] = 256 / params.posterizeAmount;
                    params['posterizeValues'] = 255 / (params.posterizeAmount - 1);
                }

                var i,
                    data = pixels.data,
                    length = data.length >> 2; //相当于除以4

                for(i=0; i < length; i++){
                    if(applyArea&&filterType=='filter-mosaic'){//只在某个区域应用马赛克
                        var firstLine = applyArea.x,           //区域第一列
                            endLine = applyArea.x+applyArea.w, //区域最后一列
                            firstRow = applyArea.y,            //区域第一行
                            endRow = applyArea.y+applyArea.h,  //区域最后一行
                            iRow = Math.floor(i/w),            //现在的i是第几行
                            iLine = i%w;                       //现在的i是第几列

                        if(!((iRow>=firstRow&&iRow<=endRow)&&(iLine>=firstLine&&iLine<=endLine)))
                            continue
                    }
                    pixels = applyFilters(filterType, params, w, pixels, i << 2, dest);
                }
            }

            // redraw the pixel data back to the working canvas
            c.putImageData(pixels,0,0);

            fn&&fn(oripixels);
        }
    }
};

H.imageFilter.getNames = function(){
    var dir = 'themes/plug_in/hhh.image.filter/';
    return [
        {name:'原图',img:dir+'ori.jpg',name_en:'filter-ori'},
        {name:'模糊',img:dir+'blur.jpg',name_en:'filter-blur'},
        {name:'描线',img:dir+'edges.jpg',name_en:'filter-edges'},
        {name:'浮雕',img:dir+'emboss.jpg',name_en:'filter-emboss'},
        {name:'矩阵',img:dir+'matrix.jpg',name_en:'filter-matrix'},
        {name:'锐化',img:dir+'sharpen.jpg',name_en:'filter-sharpen'},
        {name:'色调',img:dir+'tint.jpg',name_en:'filter-tint'},
        {name:'变灰',img:dir+'grey.jpg',name_en:'filter-greyscale'},
        //{name:'马赛克',img:dir+'mosaic.jpg',name_en:'filter-mosaic'},
        {name:'噪点',img:dir+'noise.jpg',name_en:'filter-noise'},
        {name:'减色',img:dir+'posterize.jpg',name_en:'filter-posterize'},
        {name:'旧色调',img:dir+'sepia.jpg',name_en:'filter-sepia'} //Sepia Tone
    ];
};

/**
 * 返回 ImageData 对象，该对象拷贝了画布的像素数据
 * @param c Context canvas对象的2d上下文，或者直接是canvas对象
 * */
H.imageFilter.getImageData = function(c){
    c = this.getContext(c);
    var w = c.width||c.canvas.width,h = c.height||c.canvas.height;
    if(w>0 && h>0){
        return(c.getImageData(0, 0,w,h));
    }else
        return false;
};

//深拷贝一个ImageData，备份用的
H.imageFilter.copyImageData = function(imgData){
    return imgData && new ImageData(new Uint8ClampedArray(imgData.data),imgData.width,imgData.height);
};

//把不知道传入参数是canvas还是其2d上下文的对象转成其上下文
H.imageFilter.getContext = function(c){
    if(c instanceof CanvasRenderingContext2D)
        return c;
    else
        return c.getContext('2d');
};

//使用imgDom创建一个与图片原始大小相同的canvas对象。
H.imageFilter.creatCanvas = function(img){
    if(!img)H.error('[H.imageFilter.creatCanvas]无参数');
    var canvas = document.createElement('canvas'),
        ctx = canvas.getContext('2d'),
        w = img.naturalWidth, //naturalWidth是图片的原尺寸
        h = img.naturalHeight;
    canvas.width = w;
    canvas.height = h;
    ctx.clearRect(0,0,w,h);
    ctx.drawImage(img,0,0,w,h);//在画布上画
    return canvas;
};

    /**
     *  计算高斯模糊 calculate gaussian blur
     *  adapted from http://pvnick.blogspot.com/2010/01/im-currently-porting-image-segmentation.html
     *  @param widthheight obj 图像的宽高，单位像素
     *  @param pixels obj 图像的输入像素数据
     *  @param amount number 参数：模糊数量（0-10，精确到2位小数点）
     * */
    function gaussianBlur(widthheight,pixels,amount){
        var width = widthheight.width;
        var width4 = width << 2;
        var height = widthheight.height;

        if(pixels){
            var data = pixels.data;

            // compute coefficients as a function of amount
            var q;
            if(amount < 0.0){
                amount = 0.0;
            }
            if(amount >= 2.5){
                q = 0.98711 * amount - 0.96330;
            }else if(amount >= 0.5){
                q = 3.97156 - 4.14554 * Math.sqrt(1.0 - 0.26891 * amount);
            }else{
                q = 2 * amount * (3.97156 - 4.14554 * Math.sqrt(1.0 - 0.26891 * 0.5));
            }

            //compute b0, b1, b2, and b3
            var qq = q * q;
            var qqq = qq * q;
            var b0 = 1.57825 + (2.44413 * q) + (1.4281 * qq ) + (0.422205 * qqq);
            var b1 = ((2.44413 * q) + (2.85619 * qq) + (1.26661 * qqq)) / b0;
            var b2 = (-((1.4281 * qq) + (1.26661 * qqq))) / b0;
            var b3 = (0.422205 * qqq) / b0;
            var bigB = 1.0 - (b1 + b2 + b3);

            var index,indexLast,pixel,ppixel,pppixel,ppppixel,c;
            // horizontal
            for(c = 0; c < 3; c++){
                for(var y = 0; y < height; y++){
                    // forward
                    index = y * width4 + c;
                    indexLast = y * width4 + ((width - 1) << 2) + c;
                    pixel = data[index];
                    ppixel = pixel;
                    pppixel = ppixel;
                    ppppixel = pppixel;
                    for (; index <= indexLast; index += 4) {
                        pixel = bigB * data[index] + b1 * ppixel + b2 * pppixel + b3 * ppppixel;
                        data[index] = pixel;
                        ppppixel = pppixel;
                        pppixel = ppixel;
                        ppixel = pixel;
                    }
                    // backward
                    index = y * width4 + ((width - 1) << 2) + c;
                    indexLast = y * width4 + c;
                    pixel = data[index];
                    ppixel = pixel;
                    pppixel = ppixel;
                    ppppixel = pppixel;
                    for (; index >= indexLast; index -= 4) {
                        pixel = bigB * data[index] + b1 * ppixel + b2 * pppixel + b3 * ppppixel;
                        data[index] = pixel;
                        ppppixel = pppixel;
                        pppixel = ppixel;
                        ppixel = pixel;
                    }
                }
            }

            // vertical
            for (c = 0; c < 3; c++) {
                for (var x = 0; x < width; x++) {
                    // forward
                    index = (x << 2) + c;
                    indexLast = (height - 1) * width4 + (x << 2) + c;
                    pixel = data[index];
                    ppixel = pixel;
                    pppixel = ppixel;
                    ppppixel = pppixel;
                    for (; index <= indexLast; index += width4) {
                        pixel = bigB * data[index] + b1 * ppixel + b2 * pppixel + b3 * ppppixel;
                        data[index] = pixel;
                        ppppixel = pppixel;
                        pppixel = ppixel;
                        ppixel = pixel;
                    }
                    // backward
                    index = (height - 1) * width4 + (x << 2) + c;
                    indexLast = (x << 2) + c;
                    pixel = data[index];
                    ppixel = pixel;
                    pppixel = ppixel;
                    ppppixel = pppixel;
                    for (; index >= indexLast; index -= width4) {
                        pixel = bigB * data[index] + b1 * ppixel + b2 * pppixel + b3 * ppppixel;
                        data[index] = pixel;
                        ppppixel = pppixel;
                        pppixel = ppixel;
                        ppixel = pixel;
                    }
                }
            }

            return(pixels);
        }
    }

    /**
     * 应用卷积矩阵 apply a convolution matrix
     *
     * */
    function applyMatrix(pixels, matrix, amount){
        var oripixels = H.imageFilter.copyImageData(pixels);
        // speed up access
        var data = pixels.data, bufferedData = oripixels.data, imgWidth = oripixels.width;

        // make sure the matrix adds up to 1
        /* 		matrix = normalizeMatrix(matrix); */

        // calculate the dimensions, just in case this ever expands to 5 and beyond
        var matrixSize = Math.sqrt(matrix.length);

        // loop through every pixel
        for (var i = 1; i < imgWidth - 1; i++){
            for (var j = 1; j < oripixels.height - 1; j++){

                // temporary holders for matrix results
                var sumR = 0,sumG = 0,sumB = 0;

                // loop through the matrix itself
                for (var h = 0; h < matrixSize; h++) {
                    for (var w = 0; w < matrixSize; w++) {

                        // get a refence to a pixel position in the matrix
                        var r = convertCoordinates(i + h - 1, j + w - 1, imgWidth) << 2;

                        // find RGB values for that pixel
                        var currentPixel = {
                            r: bufferedData[r],
                            g: bufferedData[r + 1],
                            b: bufferedData[r + 2]
                        };

                        // apply the value from the current matrix position
                        sumR += currentPixel.r * matrix[w + h * matrixSize];
                        sumG += currentPixel.g * matrix[w + h * matrixSize];
                        sumB += currentPixel.b * matrix[w + h * matrixSize];
                    }
                }

                // get a reference for the final pixel
                var ref = convertCoordinates(i, j, imgWidth) << 2;
                var thisPixel = {
                    r: data[ref],
                    g: data[ref + 1],
                    b: data[ref + 2]
                };

                // finally, apply the adjusted values
                data = setRGB(data, ref,
                    findColorDifference(amount, sumR, thisPixel.r),
                    findColorDifference(amount, sumG, thisPixel.g),
                    findColorDifference(amount, sumB, thisPixel.b));
            }
        }

        // code to clean the secondary canvas out of the DOM would be good here

        return(pixels);
    }

    // find a specified distance between two colours
    function findColorDifference(dif, dest, src) {
        return(dif * dest + (1 - dif) * src);
    }

    // convert x/y coordinates to pixel index reference
    function convertCoordinates(x, y, w) {
        return x + (y * w);
    }
    // throw three new RGB values into the pixels object at a specific spot
    function setRGB(data, index, r, g, b){
        data[index] = r;
        data[index + 1] = g;
        data[index + 2] = b;
        return data;
    }

    // parse a shorthand or longhand hex string, with or without the leading '#', into something usable
    function createColor(src){
        // strip the leading #, if it exists
        src = src.replace(/^#/, '');
        // if it's shorthand, expand the values
        if (src.length == 3) {
            src = src.replace(/(.)/g, '$1$1');
        }
        return(src);
    }

    /**
     * the function that actually manipulates the pixels
     * @param filterType string 滤镜名
     * @param params obj 参数
     * @param w int 图像宽度
     * @param pixels ImageData 完整的 ImageData 对象
     * @param index int 这个值表达的是：要改的像素的r值是 ImageData 里的第几个，不需要乘以4（ImageData数组里，每四个子项为一个像素，这4个子项分别代表rgba）
     * @param dest obj 也是像素信息，tint专用的
     * @return ImageData
     * */
    function applyFilters(filterType,params,w,pixels,index,dest){
        // speed up access
        var data = pixels.data, val;
        var imgWidth = w;
        var thisPixel = {r: data[index], g: data[index + 1], b: data[index + 2]}; //要修改的像素的信息 {r: 212, g: 23, b: 85}  get each colour value of current pixel

        // figure out which filter to apply, and do it
        switch(filterType){
            case "filter-greyscale":
                val = (thisPixel.r * 0.21) + (thisPixel.g * 0.71) + (thisPixel.b * 0.07);
                data = setRGB(data, index,
                    findColorDifference(params.greyscaleOpacity, val, thisPixel.r),
                    findColorDifference(params.greyscaleOpacity, val, thisPixel.g),
                    findColorDifference(params.greyscaleOpacity, val, thisPixel.b));
                break;

            case "filter-mosaic": //马赛克
                // a bit more verbose to reduce amount of math necessary
                var pos = index >> 2; //这是 ImageData 里的第几个像素
                var stepY = Math.floor(pos / imgWidth);
                var stepY1 = stepY % params.mosaicSize;
                var stepX = pos - (stepY * imgWidth);
                var stepX1 = stepX % params.mosaicSize;

                if (stepY1) pos -= stepY1 * imgWidth;
                if (stepX1) pos -= stepX1;
                pos = pos << 2;

                data = setRGB(data, index,
                    findColorDifference(params.mosaicOpacity, data[pos], thisPixel.r),
                    findColorDifference(params.mosaicOpacity, data[pos + 1], thisPixel.g),
                    findColorDifference(params.mosaicOpacity, data[pos + 2], thisPixel.b));
                break;

            case "filter-noise":
                val = noise(params.noiseAmount);

                if ((params.noiseType == "mono") || (params.noiseType == "monochrome")) {
                    data = setRGB(data, index,
                        checkRGBBoundary(thisPixel.r + val),
                        checkRGBBoundary(thisPixel.g + val),
                        checkRGBBoundary(thisPixel.b + val));
                } else {
                    data = setRGB(data, index,
                        checkRGBBoundary(thisPixel.r + noise(params.noiseAmount)),
                        checkRGBBoundary(thisPixel.g + noise(params.noiseAmount)),
                        checkRGBBoundary(thisPixel.b + noise(params.noiseAmount)));
                }
                break;

            case "filter-posterize":
                //console.log( params.posterizeAreas); //,parseInt(params.posterizeValues * parseInt(thisPixel.r / params.posterizeAreas))
                data = setRGB(data, index,
                    findColorDifference(params.posterizeOpacity, parseInt(params.posterizeValues * parseInt(thisPixel.r / params.posterizeAreas)), thisPixel.r),
                    findColorDifference(params.posterizeOpacity, parseInt(params.posterizeValues * parseInt(thisPixel.g / params.posterizeAreas)), thisPixel.g),
                    findColorDifference(params.posterizeOpacity, parseInt(params.posterizeValues * parseInt(thisPixel.b / params.posterizeAreas)), thisPixel.b));
                break;

            case "filter-sepia":
                data = setRGB(data, index,
                    findColorDifference(params.sepiaOpacity, (thisPixel.r * 0.393) + (thisPixel.g * 0.769) + (thisPixel.b * 0.189), thisPixel.r),
                    findColorDifference(params.sepiaOpacity, (thisPixel.r * 0.349) + (thisPixel.g * 0.686) + (thisPixel.b * 0.168), thisPixel.g),
                    findColorDifference(params.sepiaOpacity, (thisPixel.r * 0.272) + (thisPixel.g * 0.534) + (thisPixel.b * 0.131), thisPixel.b));
                break;

            case "filter-tint":
                data = setRGB(data, index,
                    findColorDifference(params.tintOpacity, dest.r, thisPixel.r),
                    findColorDifference(params.tintOpacity, dest.g, thisPixel.g),
                    findColorDifference(params.tintOpacity, dest.b, thisPixel.b));
                break;


        }
        return(pixels);
    }

    // calculate random noise. different every time!
    function noise(noiseValue) {
        return Math.floor((noiseValue >> 1) - (Math.random() * noiseValue));
    }

    // ensure an RGB value isn't negative / over 255
    function checkRGBBoundary(val) {
        if (val < 0) {
            return 0;
        } else if (val > 255) {
            return 255;
        } else {
            return val;
        }
    }

//给canvas加上水印
H.waterMark = function(){

};
H.waterMark.getNames = function(){
    var dir = 'themes/plug_in/hhh.image.filter/';
    return [
        {name:'无水印',img:dir+'ori.jpg',name_en:'water-ori'},
        {name:'无水印',img:dir+'ori.jpg',source:'',name_en:'water-ori'}
    ];
};


//==== 其它工具 =====================
/**
 * @param filterType string 工具名，是 getNames 里面的 name_en
 *
 * @param option obj
 * @param option.params obj 对工具的设置
 * @param option.canvas DOM canvasDOM 改完之后更新的canvas
 * @param option.content obj canvas的2d上下文
 * @param option.oripixel imageData 作为数据源的图像原始数据，如果没有这个，则使用canvasContext作为数据源
 * @param option.applyArea 要应用滤镜的像素区域，零点在canvas左上角 {x:5,y:5,w:50,h:50} 目前只有马赛克用到
 *
 * @param fn function 工具应用完毕之后
 * */
H.imageTools = function(filterType,option,fn){
    if(!filterType)H.error('[H.imageTools]滤镜名的参数为空，你个2b程序猿');

    option = option || {};
    option.params = option.params || {};
    var applyArea = option.applyArea;

    if(filterType=='filter-mosaic'){
        option.params.mosaicOpacity = option.params.mosaicOpacity || 0.89;
        option.params.mosaicSize = option.params.mosaicSize || 18;
        if(!applyArea)H.error('[H.imageTools]'+filterType+'必须指定滤镜的应用区域（option.applyArea）');
        H.imageFilter(filterType,option,fn);
        return;
    }

    var canvas = option.canvas,
        c = option.content || this.imageFilter.getContext(canvas),
        w = c.width = option.width || c.canvas.width,
        h = c.height = option.height || c.canvas.height;
    //console.log(option.oripixel);
    if(c){

    }
};
H.imageTools.getNames = function(){
    var dir = 'themes/plug_in/hhh.image.filter/';
    return [
        {name:'马赛克',img:dir+'mosaic.jpg',name_en:'filter-mosaic'}
    ];
}

}());